﻿using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using LFSRecord2.Model;

namespace LFSRecord2.View.ThreeD
{
    public class Layer3DObject : DependencyObject, INotifyPropertyChanged
    {
        private GeometryModel3D LayerModelGeometry;
        public ModelVisual3D LayerModelView { get; set; }
        public CompLayerBase Layer { get; set; }

        public static readonly DependencyProperty DoneUpdatingCurrentValuesProperty = DependencyProperty.Register(
            "DoneUpdatingCurrentValues",
            typeof(bool),
            typeof(Layer3DObject),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnDoneUpdatingCurrentValuesChanged))
        );
        public bool DoneUpdatingCurrentValues
        {
            get { return (bool)GetValue(DoneUpdatingCurrentValuesProperty); }
            set { SetValue(DoneUpdatingCurrentValuesProperty, value); }
        }
        private static void OnDoneUpdatingCurrentValuesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //Debug.WriteLine("Updating null layer 3D object - " + ((Null3DObject)d).Layer.LayerName);
            Layer3DObject layerObj = (Layer3DObject)d;
            if (layerObj.Layer.LayerType == CompLayerTypes.Null || layerObj.Layer.LayerType == CompLayerTypes.ReplayData)
                layerObj.ApplyTransform();
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }

        public Layer3DObject(CompLayerBase layer)
        {
            LayerModelView = new ModelVisual3D();
            Layer = layer;

            // Create bindings
            Binding myBinding = new Binding("DoneUpdatingCurrentValues");
            myBinding.Source = Layer;
            BindingOperations.SetBinding(this, Layer3DObject.DoneUpdatingCurrentValuesProperty, myBinding);
            
            // Create mesh
            MeshGeometry3D layerMesh = new MeshGeometry3D();
            MaterialGroup matGroup;
            if (Layer.LayerType == CompLayerTypes.Null)
            {
                Point3D p0 = new Point3D();
                Point3D p1 = new Point3D(100, 0, 0);
                Point3D p2 = new Point3D(100, 0, 100);
                Point3D p3 = new Point3D(0, 0, 100);
                layerMesh.Positions.Add(p0);
                layerMesh.Positions.Add(p1);
                layerMesh.Positions.Add(p2);
                layerMesh.Positions.Add(p3);
                layerMesh.TriangleIndices.Add(0);
                layerMesh.TriangleIndices.Add(1);
                layerMesh.TriangleIndices.Add(2);
                layerMesh.TriangleIndices.Add(0);
                layerMesh.TriangleIndices.Add(2);
                layerMesh.TriangleIndices.Add(3);
                layerMesh.Normals.Add(CalculateNormal(p0, p1, p2));
                layerMesh.Normals.Add(CalculateNormal(p0, p2, p3));

                matGroup = new MaterialGroup();
                matGroup.Children.Add(new DiffuseMaterial(Brushes.Black));
                matGroup.Children.Add(new EmissiveMaterial(Brushes.Red));
                LayerModelGeometry = new GeometryModel3D(layerMesh, matGroup);
                LayerModelGeometry.BackMaterial = matGroup;

                // Add it to model
                LayerModelView.Content = LayerModelGeometry;
            }
            else if (Layer.LayerType == CompLayerTypes.ReplayData)
            {
                Point3D p0 = new Point3D(-5, 0, 5);
                Point3D p1 = new Point3D(-5, 0, -5);
                Point3D p2 = new Point3D(5, 0, -5);
                Point3D p3 = new Point3D(5, 0, 5);
                layerMesh.Positions.Add(p0);
                layerMesh.Positions.Add(p1);
                layerMesh.Positions.Add(p2);
                layerMesh.Positions.Add(p3);
                layerMesh.TriangleIndices.Add(0);
                layerMesh.TriangleIndices.Add(1);
                layerMesh.TriangleIndices.Add(2);
                layerMesh.TriangleIndices.Add(0);
                layerMesh.TriangleIndices.Add(2);
                layerMesh.TriangleIndices.Add(3);
                layerMesh.Normals.Add(CalculateNormal(p0, p1, p2));
                layerMesh.Normals.Add(CalculateNormal(p0, p2, p3));

                matGroup = new MaterialGroup();
                matGroup.Children.Add(new DiffuseMaterial(Brushes.Black));
                matGroup.Children.Add(new EmissiveMaterial(Brushes.Blue));
                LayerModelGeometry = new GeometryModel3D(layerMesh, matGroup);
                LayerModelGeometry.BackMaterial = matGroup;

                // Add it to model
                LayerModelView.Content = LayerModelGeometry;
            }
        }

        private static Vector3D CalculateNormal(Point3D p0, Point3D p1, Point3D p2)
        {
            Vector3D v0 = new Vector3D(
                p1.X - p0.X, p1.Y - p0.Y, p1.Z - p0.Z);
            Vector3D v1 = new Vector3D(
                p2.X - p1.X, p2.Y - p1.Y, p2.Z - p1.Z);
            return Vector3D.CrossProduct(v0, v1);
        }

        public void ApplyTransform()
        {
            //Debug.WriteLine("applying null object transform");
            Transform3DGroup trGroup = new Transform3DGroup();
            if (Layer.LayerType == CompLayerTypes.Null)
            {
                CompLayerNull nullLayer = (CompLayerNull)Layer;
                Point3D anchorPoint = new Point3D(
                    ((CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Anchor]).CurrentXValue,
                    ((CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Anchor]).CurrentYValue,
                    ((CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Anchor]).CurrentZValue
                    );

                trGroup.Children.Add(new ScaleTransform3D(
                    ((CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Scale]).CurrentXValue,
                    ((CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Scale]).CurrentYValue,
                    ((CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Scale]).CurrentZValue,
                    anchorPoint.X,
                    anchorPoint.Y,
                    anchorPoint.Z
                    ));

                trGroup.Children.Add(new RotateTransform3D(
                    new AxisAngleRotation3D(new Vector3D(0, 2, 0), ((CompLayerPropertyDouble)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.RotateY]).CurrentValue),
                        anchorPoint.X, anchorPoint.Y, anchorPoint.Z));
                trGroup.Children.Add(new RotateTransform3D(
                    new AxisAngleRotation3D(new Vector3D(0, 0, 2), -((CompLayerPropertyDouble)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.RotateZ]).CurrentValue),
                        anchorPoint.X, anchorPoint.Y, anchorPoint.Z));
                trGroup.Children.Add(new RotateTransform3D(
                    new AxisAngleRotation3D(new Vector3D(2, 0, 0), ((CompLayerPropertyDouble)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.RotateX]).CurrentValue),
                        anchorPoint.X, anchorPoint.Y, anchorPoint.Z));
                trGroup.Children.Add(new TranslateTransform3D(
                    ((CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Position]).CurrentXValue - anchorPoint.X,
                    -((CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Position]).CurrentYValue - anchorPoint.Y,
                    ((CompLayerPropertyPoint3D)nullLayer.Properties[(int)CompLayerNull.PropertyTypes.Position]).CurrentZValue - anchorPoint.Z
                    ));
            }
            else if (Layer.LayerType == CompLayerTypes.ReplayData)
            {
                CompLayerReplayData rdLayer = (CompLayerReplayData)Layer;

                trGroup.Children.Add(new RotateTransform3D(
                    new AxisAngleRotation3D(new Vector3D(0, 0, 2), -((CompLayerPropertyDoubleRD)rdLayer.Properties[(int)CompLayerReplayData.PropertyTypes.Heading]).CurrentValue),
                        0, 0, 0));
                trGroup.Children.Add(new TranslateTransform3D(
                    ((CompLayerPropertyPoint3DRD)rdLayer.Properties[(int)CompLayerReplayData.PropertyTypes.Position]).CurrentXValue,
                    -((CompLayerPropertyPoint3DRD)rdLayer.Properties[(int)CompLayerReplayData.PropertyTypes.Position]).CurrentYValue,
                    ((CompLayerPropertyPoint3DRD)rdLayer.Properties[(int)CompLayerReplayData.PropertyTypes.Position]).CurrentZValue
                    ));
            }

            LayerModelGeometry.Transform = trGroup;
        }
    }
}
